package restful

import (
	"crypto/md5"
	"encoding/base64"
	"github.com/gorilla/mux"
	"github.com/satori/go.uuid"
	"log"
	"moss/conf"
	"moss/status"
	"net/http"
	"net/http/httputil"
	"regexp"
	"runtime/debug"
	"strconv"
	"strings"
	"net/url"
)

func verboseErrorLog(req *http.Request, e status.Status) {
	log.Println("Error", status.GetCodeString(e))
	log.Println("=== Backtrace ===")
	stackLines := strings.Split(string(debug.Stack()), "\n")
	for i := range stackLines {
		if strings.HasPrefix(stackLines[i], "moss/restful") {
			println(stackLines[i])
			if i+1 < len(stackLines) {
				println(stackLines[i+1])
			}
		}
	}

	log.Println("=== Request ===")
	b, err := httputil.DumpRequest(req, false)
	if err == nil {
		println(string(b))
	}
}

func getUserDefinedHeader(req *http.Request) (m map[string]string) {
	if str := req.Header.Get(conf.HTTPHeaderResponseContentType); len(str) != 0 {
		m[conf.HTTPHeaderResponseContentType] = str
	}
	if str := req.Header.Get(conf.HTTPHeaderResponseContentLanguage); len(str) != 0 {
		m[conf.HTTPHeaderResponseContentLanguage] = str
	}
	if str := req.Header.Get(conf.HTTPHeaderResponseExpires); len(str) != 0 {
		m[conf.HTTPHeaderResponseExpires] = str
	}
	if str := req.Header.Get(conf.HTTPHeaderResponseCacheControl); len(str) != 0 {
		m[conf.HTTPHeaderResponseCacheControl] = str
	}
	if str := req.Header.Get(conf.HTTPHeaderResponseContentDisposition); len(str) != 0 {
		m[conf.HTTPHeaderResponseContentDisposition] = str
	}
	if str := req.Header.Get(conf.HTTPHeaderResponseContentEncoding); len(str) != 0 {
		m[conf.HTTPHeaderContentEncoding] = str
	}
	return m
}

func hasGzip(req *http.Request) bool {
	return strings.Contains(req.Header.Get(conf.HTTPHeaderAcceptEncoding), "gzip")
}

func addHeaderOptions(w http.ResponseWriter, headers map[string]string) {
	if headers == nil {
		return
	}
	for k, v := range headers {
		if len(k) != 0 && len(v) != 0 {
			w.Header().Set(k, v)
		}
	}
}

func calculateMD5(body []byte) string {
	md5hash := md5.New()
	md5hash.Write(body)
	return base64.StdEncoding.EncodeToString(md5hash.Sum(nil))
}

func between(str, start, end string) string {
	s := strings.Index(str, start)
	if s < 0 {
		return ""
	}
	s += len(start)
	e := strings.Index(str[s:], end)
	if e < 0 {
		return ""
	}
	return str[s : s+e]
}

func getAccessKeyId(req *http.Request) string {
	rawAuthStr := req.Header.Get(conf.HTTPHeaderAuthorization)
	// something between "Credential=" and "/"
	return between(rawAuthStr, "Credential=", "/")
}

func getHostName(req *http.Request) string {
	host := req.Header.Get("Host")
	// Two methods are both OK
	if len(host) == 0 {
		host = req.Host
	}

	return host
}

func getDataCenterName(req *http.Request) string {
	rawAuthStr := req.Header.Get(conf.HTTPHeaderAuthorization)
	tokens := strings.Split(between(rawAuthStr, "Credential=", ","), "/")
	if len(tokens) != 5 {
		return ""
	}

	return tokens[2]
}

func getBucketName(req *http.Request) string {
	return mux.Vars(req)["bucket"]
}

func getObjectName(req *http.Request) string {
	path := mux.Vars(req)["object"]
	path, _ = url.PathUnescape(path)
	path = strings.TrimSuffix(path, "/")
	return path
}

func getUploadId(req *http.Request) string {
	return mux.Vars(req)["uploadId"]
}

func getObjectNamePost(formFileKey, uploadFilename string) string {
	objectName := formFileKey
	fileNames := strings.Split(uploadFilename, "/")
	fileName := fileNames[len(fileNames)-1]
	if strings.HasSuffix(objectName, conf.OssFormFieldKeyValueSuffix) {
		objectName = strings.TrimSuffix(objectName, conf.OssFormFieldKeyValueSuffix) + fileName
	}
	return objectName
}

func generateRequestId() string {
	u, _ := uuid.NewV4()
	return strings.Replace(u.String(), "-", "", 4)
}

func getQueryInt(req *http.Request, key string, defaultValue int) int {
	valueRaw := req.URL.Query().Get(key)
	if len(valueRaw) == 0 {
		return defaultValue
	}

	value, err := strconv.Atoi(valueRaw)
	if err != nil {
		return defaultValue
	}

	return value
}

func getHeaderString(req *http.Request, key string, defaultValue string) string {
	value := req.Header.Get(key)
	if len(value) == 0 {
		return defaultValue
	}

	return value
}

func praseRangeString(s string) (bool, int64, int64) {
	match, err := regexp.MatchString("bytes=[\\d]+-[\\d]+", s)
	if err != nil || !match {
		return false, 0, 0
	}

	r := regexp.MustCompile("[\\d]+")
	tokenList := r.FindAllString(s, 2)
	if len(tokenList) < 2 {
		return false, 0, 0
	}

	startIdx, err := strconv.ParseInt(tokenList[0], 10, 64)
	if err != nil {
		return false, 0, 0
	}

	stopIdx, err := strconv.ParseInt(tokenList[1], 10, 64)
	if err != nil {
		return false, 0, 0
	}

	if startIdx < 0 || startIdx > stopIdx {
		return false, 0, 0
	}

	return true, startIdx, stopIdx
}

func getUserMeta(req *http.Request) string {
	var userMeta []string
	for k, v := range req.Header {
		if strings.HasPrefix(k, conf.HTTPHeaderOssMetaPrefix) && len(v) != 0 && len(v[0]) != 0 {
			userMeta = append(userMeta, k)
			userMeta = append(userMeta, v[0])
		}
	}

	// Five predefined header entries in PutObject
	headers := getUserDefinedHeader(req)
	for k, v := range headers {
		if len(v) != 0 {
			userMeta = append(userMeta, k)
			userMeta = append(userMeta, v)
		}
	}

	return strings.Join(userMeta, conf.UserMeaSep)
}

func setUserMeta(marshal string) map[string]string {
	headerMap := make(map[string]string)
	splitTokens := strings.Split(marshal, conf.UserMeaSep)
	for i := 0; i+1 < len(splitTokens); i += 2 {
		headerMap[splitTokens[i]] = splitTokens[i+1]
	}
	return headerMap
}

func getCopySourceBucketName(req *http.Request) string {
	copySource := req.Header.Get(conf.HTTPHeaderOssCopySource)
	if len(copySource) == 0 {
		return ""
	}

	if copySource[0] == '/' {
		copySource = copySource[1:]
	}

	str := strings.SplitN(copySource, "/", 2)
	if len(str) != 2 {
		log.Println(str)
		return ""
	}

	return str[0]
}

func getCopySourceObjectName(req *http.Request) string {
	copySource := req.Header.Get(conf.HTTPHeaderOssCopySource)
	if len(copySource) == 0 {
		return ""
	}

	if copySource[0] == '/' {
		copySource = copySource[1:]
	}

	str := strings.SplitN(copySource, "/", 2)
	if len(str) != 2 {
		log.Println(str)
		return ""
	}

	return str[1]
}
